package com.squirrel.common.util;

import com.squirrel.common.exception.BaseException;
import com.squirrel.common.param.video.MultipartFileParam;
import com.squirrel.common.response.code.CodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import java.io.*;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.UUID;

/**
 * @Description 文件上传
 * @Author 关远键
 * @Version v1.0.0
 * @Date 2021/11/25 14:26
 */
@Slf4j
public class FileUploadUtils {

/*
    private static final int CHUNK_SIZE = 10;

    public static String upload(MultipartFileParam fileParam, String destPath) {
        boolean chunkFlag = fileParam.isChunkFlag();
        if (chunkFlag) {
            return chunkUpload(fileParam, destPath);
        }
        return singleUpload(fileParam, destPath);
    }

    private static String singleUpload(MultipartFileParam fileParam, String destPath) {
        MultipartFile file = fileParam.getFile();
        File base = new File(destPath);
        if (!base.exists()) {
            base.mkdirs();
        }
        try {
            file.transferTo(new File(base, "yuanjian" + file.getOriginalFilename()));
//            Date date = new Date();
////            new File
//            String filePath = destPath + File.separator + file.getOriginalFilename();
//            long size = FileUtils.sizeOf(new File(filePath));
//            String sizeStr = size / (1024 * 1024) + "MB";
        } catch (IOException e) {
            e.printStackTrace();
            return "上传出错了";
        }
        return "success";

    }

    private static String chunkUpload(MultipartFileParam fileParam, String destPath) {
        boolean lastChunk = false;
        int chunkNumber = fileParam.getChunkNumber();
        int totalChunks = fileParam.getTotalChunks();
        MultipartFile file = fileParam.getFile();
        String filename = file.getOriginalFilename();

        String parentPath = destPath + File.separator + filename;
        String tempFilename = filename + "_tmp";

        try {
            tmpFile(parentPath, tempFilename, file, chunkNumber, totalChunks, null);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("chunkUpload出错了");
        }
        return null;


    }

    private static File tmpFile(String parentDir, String tempFileName, MultipartFile file,
                         int currentChunk, long totalSize, String fileMd5) throws IOException {
        log.info("开始上传文件, 时间是:{}, 文件名称是:{}", LocalDate.now(), tempFileName);
        long position = (currentChunk - 1) * CHUNK_SIZE;
        File tmpDir = new File(parentDir);
        File tmpFile = new File(parentDir, tempFileName);

        if (!tmpDir.exists()) {
            tmpDir.mkdirs();
        }

        RandomAccessFile tempRaf = new RandomAccessFile(tmpFile, "rw");
        if (tempRaf.length() == 0) {
            tempRaf.setLength(totalSize);
        }

        // 写入该分片数据
        FileChannel fc = tempRaf.getChannel();
        MappedByteBuffer map = fc.map(FileChannel.MapMode.READ_WRITE, position, file.getSize());
        map.put(file.getBytes());
//        clean(map);
        fc.close();
        tempRaf.close();

        // 记录已经完成的分片
//        FileChunkRecord fileChunkRecord = new FileChunkRecord();
//        fileChunkRecord.setMd5(fileMd5).setUploadStatus(1).setChunk(currentChunk);
//        fileChunkRecordMapper.insert(fileChunkRecord);
//        log.info("文件上传完成, 时间是:{}, 文件名称是:{}", DateUtil.now(), tempFileName);
        return tmpFile;
    }
*/

    public static String chunkUploadByMappedByteBuffer(MultipartFileParam param, String filePath) {

        // 文件传输任务ID
        if(param.getTaskId() == null || "".equals(param.getTaskId())){
            param.setTaskId(UUID.randomUUID().toString().replace("-", ""));
        }

        // 文件名
        String originalFilename = param.getFile().getOriginalFilename();
        String suffix = originalFilename.substring(originalFilename.lastIndexOf('.'));
        //前端传文件名不带后缀
//        String fileName = param.getNickname() + "-" + param.getFilename() + suffix;
        //前端传文件名带后缀
        String fileName = param.getNickname() + "-" + param.getFilename();
        log.info("文件名：{}你为什么还有后缀", fileName);

        // 临时文件名
        String tempFileName = param.getTaskId() + fileName + "_tmp";

        // 创建文件对象
        File fileDir = new File(filePath);
        if(!fileDir.exists()){
            System.out.println("目录不存在时创建:" + fileDir.mkdirs());
        }

        File tempFile = new File(filePath, tempFileName);

        try {
            //第一步：随机访问文件流
            RandomAccessFile raf = new RandomAccessFile(tempFile,"rw");
            //第二步：文件关联对象？？？
            FileChannel fileChannel = raf.getChannel();
            //第三步 计算偏移量（开始位置）
            long position = (param.getChunkNumber() - 1) * param.getChunkSize();
            //第四步：文件字节数组？？
            byte[] fileData = param.getFile().getBytes();
            //第五步；当前位置+文件字节长度
//        long end = position + fileData.length - 1;

            // 从当前位置写入数据
            fileChannel.position(position);
            fileChannel.write(ByteBuffer.wrap(fileData));

            // 强制写入
            fileChannel.force(true);
            fileChannel.close();
            raf.close();
        } catch (IOException e) {
            log.error("视频写入出错了");
            throw new BaseException(CodeEnum.FAIL);
        }

        //第八步
        boolean isComplete = checkUploadStatus(param, fileName, filePath);

        // 改名
        if(isComplete){
            renameFile(tempFile, fileName);
        }

        System.out.println(fileName + isComplete);
        log.info("视频【{}】传输是否已完成：{}", fileName, isComplete ? "已完成" : "等待下一分片");
        return param.getTaskId();
    }

    /**
     * 文件重命名
     * @param toBeRenamed   将要修改名字的文件
     * @param toFileNewName 新的名字
     * @return
     */
    public static void renameFile(File toBeRenamed, String toFileNewName) {
        //检查要重命名的文件是否存在，是否是文件
        if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
            System.out.println("文件不存在");
            return;
        }

        // 父级路径
        String p = toBeRenamed.getParent();
        File newFile = new File(p + File.separatorChar + toFileNewName);

        //修改文件名
        toBeRenamed.renameTo(newFile);
    }

    /**
     * 检查文件上传进度
     * @return
     */
    public static boolean checkUploadStatus(MultipartFileParam param, String fileName, String filePath) {
        File confFile = new File(filePath,fileName+".conf");
        try {
            RandomAccessFile confAccessFile = new RandomAccessFile(confFile,"rw");
            //文件长度
            confAccessFile.setLength(param.getTotalChunks());
            //起始偏移量
            confAccessFile.seek(param.getChunkNumber() - 1);
            //写入文件
            confAccessFile.write(Byte.MAX_VALUE);

            byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
            confAccessFile.close(); //不关闭会造成无法占用

            //创建conf文件文件长度为总分片数，每上传一个分块即向conf文件中写入一个127，
            // 那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127
            for (byte b : completeStatusList) {
                if (b != Byte.MAX_VALUE) {
                    return false;
                }
            }

            //上传完成，删除conf文件
            return confFile.delete();
        } catch (IOException e) {
            log.error("视频检查进度时出错了");
            throw new BaseException(CodeEnum.FAIL);
        }

    }

    /**
     * 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash，在并发情况下很容易发生
     * 正在释放时另一个线程正开始读取，于是crash就发生了。所以为了系统稳定性释放前一般需要检 查是否还有线程在读或写
     * @param mappedByteBuffer
     */
    public static void freedMappedByteBuffer(final MappedByteBuffer mappedByteBuffer) {
        try {
            if (mappedByteBuffer == null) {
                return;
            }
            mappedByteBuffer.force();
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    try {
                        Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
                        //可以访问private的权限
                        getCleanerMethod.setAccessible(true);
                        //在具有指定参数的 方法对象上调用此 方法对象表示的底层方法
//                        sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer,
//                                new Object[0]);
//                        cleaner.clean();
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.out.println("清理缓存出错!!!"+e.getMessage());
                    }
                    System.out.println("缓存清理完毕!!!");
                    return null;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}
